﻿Imports System.Data.SqlServerCe
Namespace Data
    Public Class ObjectPersister
        Public Shared Function GetOne(Of T)(ByVal whereClause As String) As T
            Return GetOneWorker(Of T)(whereClause, Nothing, Nothing)
        End Function
        Public Shared Function GetOne(Of T)(ByVal whereClause As String, ByVal ParamArray parameters() As SqlCeParameter) As T
            Return GetOneWorker(Of T)(whereClause, Nothing, parameters)
        End Function
        Public Shared Function GetOne(Of T)(ByVal whereClause As String, ByVal transactionScope As TransactionScope) As T
            Return GetOneWorker(Of T)(whereClause, transactionScope, Nothing)
        End Function
        Public Shared Function GetOne(Of T)(ByVal whereClause As String, ByVal transactionScope As TransactionScope, ByVal ParamArray parameters() As SqlCeParameter) As T
            Return GetOneWorker(Of T)(whereClause, transactionScope, parameters)
        End Function

        Private Shared Function GetOneWorker(Of T)(ByVal whereClause As String, ByVal transactionScope As TransactionScope, ByVal parameters() As SqlCeParameter) As T
            Dim clsTypeDef As TypeDefinition = GetTypeDef(GetType(T))

            Dim sqlCmd As Command = GenerateSelectCommand(clsTypeDef, whereClause, Nothing, transactionScope)
            If parameters IsNot Nothing AndAlso parameters.Count > 0 Then sqlCmd.AddParameterRange(parameters)

            If sqlCmd.Transaction Is Nothing Then sqlCmd.Connection.Open()
            Try
                Environment.OnDBTraceInfoCommand(sqlCmd)
                Using dr As SqlCeDataReader = sqlCmd.ExecuteReader
                    If dr.Read Then
                        Return DRToObject(Of T)(dr, clsTypeDef)
                    Else
                        Return Nothing
                    End If
                End Using
            Finally
                If sqlCmd.Transaction Is Nothing Then sqlCmd.Connection.Close()
            End Try
        End Function

        Public Shared Function GetAll(Of T)() As Generic.List(Of T)
            Return GetAllWorker(Of T)(Nothing, Nothing, Nothing, Nothing)
        End Function

        Public Shared Function GetAll(Of T)(ByVal parameters() As SqlCeParameter, ByVal whereClause As String) As Generic.List(Of T)
            Return GetAllWorker(Of T)(whereClause, Nothing, parameters, Nothing)
        End Function
        Public Shared Function GetAll(Of T)(ByVal whereClause As String, ByVal transactionScope As TransactionScope, ByVal ParamArray parameters() As SqlCeParameter) As Generic.List(Of T)
            Return GetAllWorker(Of T)(whereClause, Nothing, parameters, transactionScope)
        End Function
        Public Shared Function GetAll(Of T)(ByVal whereClause As String, ByVal ParamArray parameters() As SqlCeParameter) As Generic.List(Of T)
            Return GetAllWorker(Of T)(whereClause, Nothing, parameters, Nothing)
        End Function

        Public Shared Function GetAll(Of T)(ByVal whereClause As String, ByVal orderByClause As String, ByVal ParamArray parameters() As SqlCeParameter) As Generic.List(Of T)
            Return GetAllWorker(Of T)(whereClause, orderByClause, parameters, Nothing)
        End Function

        Private Shared Function GetAllWorker(Of T)(ByVal whereClause As String, ByVal orderByClause As String, ByVal parameters() As SqlCeParameter, ByVal transactionScope As TransactionScope) As Generic.List(Of T)
            Dim clsTypeDef As TypeDefinition = GetTypeDef(GetType(T))

            Dim sqlCmd As Command = GenerateSelectCommand(clsTypeDef, whereClause, orderByClause, transactionScope)
            If parameters IsNot Nothing AndAlso parameters.Count > 0 Then sqlCmd.AddParameterRange(parameters)

            Dim aRet As New Generic.List(Of T)
            If sqlCmd.Transaction Is Nothing Then sqlCmd.Connection.Open()
            Try
                Environment.OnDBTraceInfoCommand(sqlCmd)
                Using dr As SqlCeDataReader = sqlCmd.ExecuteReader
                    While dr.Read
                        aRet.Add(DRToObject(Of T)(dr, clsTypeDef))
                    End While
                End Using
            Finally
                If sqlCmd.Transaction Is Nothing Then sqlCmd.Connection.Close()
            End Try
            Return aRet
        End Function

        Private Shared Function GenerateSelectCommand(ByVal typeDef As TypeDefinition, ByVal whereClause As String, ByVal orderByClause As String, ByVal transactionScope As TransactionScope) As Command
            Dim clsSql As New System.Text.StringBuilder(typeDef.SelectSqlStatement)
            If Not String.IsNullOrEmpty(whereClause) Then
                clsSql.Append(" WHERE ")
                clsSql.Append(whereClause)
            End If
            If Not String.IsNullOrEmpty(orderByClause) Then
                clsSql.Append(" ORDER BY ")
                clsSql.Append(orderByClause)
            End If

            If transactionScope IsNot Nothing AndAlso transactionScope.IsNullTransaction Then
                Return Environment.NewCommand(clsSql.ToString, transactionScope)
            Else
                Return Environment.NewCommand(clsSql.ToString)
            End If
        End Function

        Public Shared Sub DefineObject(ByVal type As Type, ByVal tableName As String, ByVal columns() As String)
            Dim sName As String = type.Name
            If mTypeDefinitions.ContainsKey(sName) Then Return

            Dim clsDef As New TypeDefinition
            clsDef.FieldCount = columns.Count
            clsDef.Type = type
            clsDef.TableName = tableName
            System.Array.Resize(clsDef.ColumnFieldInfos, columns.Count)
            System.Array.Resize(clsDef.CustomSetHandlers, columns.Count)
            Dim clsSql As New System.Text.StringBuilder("SELECT ")
            For i As Integer = 0 To columns.Count - 1
                Dim sColDef As String = columns(i)
                Dim sTableColName As String
                Dim sFieldName As String
                Dim iPos As Integer = sColDef.IndexOf("/"c)
                If iPos > 0 Then
                    sTableColName = sColDef.Substring(0, iPos)
                    sFieldName = sColDef.Substring(iPos + 1)
                Else
                    sTableColName = sColDef
                    sFieldName = sColDef
                End If

                If i = 0 Then
                    clsSql.Append("[")
                Else
                    clsSql.Append(",[")
                End If
                clsSql.Append(sTableColName)
                clsSql.Append("]"c)
                If sFieldName = "[CUSTOM]" Then
                Else
                    clsDef.ColumnFieldInfos(i) = type.GetField(sFieldName)
                    If clsDef.ColumnFieldInfos(i) Is Nothing Then
                        Throw New ArgumentException(String.Format("Field {0} not found on type {1}", sFieldName, sName))
                    End If
                End If
            Next
            clsSql.Append(" FROM ")
            clsSql.Append(tableName)
            clsDef.SelectSqlStatement = clsSql.ToString
            mTypeDefinitions.Add(sName, clsDef)
        End Sub

        Public Delegate Sub RowHandlerDelegate(ByVal obj As Object)
        Public Shared Sub SetRowCompleteHandler(ByVal type As System.Type, ByVal handler As RowHandlerDelegate)
            Dim clsTD As TypeDefinition = GetTypeDef(type)
            If clsTD Is Nothing Then Throw New ApplicationException("Type not defined!")
            clsTD.RowCompleteHandler = handler
        End Sub

        Public Delegate Sub SetCustomFieldDelegate(ByVal obj As Object, ByVal value As Object)
        'Public Delegate Function GetCustomFieldDelegate(ByVal obj As Object) As Object
        Public Shared Sub AddCustomFieldHandler(ByVal type As System.Type, ByVal fieldName As String, ByVal setHandler As SetCustomFieldDelegate)
            Dim clsTD As TypeDefinition = GetTypeDef(type)
            If clsTD Is Nothing Then Throw New ApplicationException("Type not defined!")

            For i As Integer = 0 To clsTD.ColumnFieldInfos.Count - 1
                If clsTD.ColumnFieldInfos(i).Name = fieldName Then
                    clsTD.CustomSetHandlers(i) = setHandler
                    Return
                End If
            Next
            Throw New ApplicationException("Field not found!")
        End Sub

        Private Shared Function GetTypeDef(ByVal type As System.Type) As TypeDefinition
            Return mTypeDefinitions.Item(type.Name)
        End Function

        Private Shared mTypeDefinitions As New Generic.Dictionary(Of String, TypeDefinition)
        Private Class TypeDefinition
            Public Type As System.Type
            Public SelectSqlStatement As String
            Public FieldCount As Integer
            Public TableName As String
            Public ColumnFieldInfos() As System.Reflection.FieldInfo
            Public CustomSetHandlers() As SetCustomFieldDelegate
            Public RowCompleteHandler As RowHandlerDelegate
        End Class

        Private Shared Function DRToObject(Of T)(ByVal dr As SqlCeDataReader, ByVal typeDef As TypeDefinition) As T
            Dim clsType As System.Type = GetType(T)
            Dim clsRet As T = CType(System.Activator.CreateInstance(GetType(T)), T)
            For i As Integer = 0 To typeDef.FieldCount - 1
                Dim oValue As Object = dr.GetValue(i)
                If oValue IsNot DBNull.Value Then
                    If typeDef.CustomSetHandlers(i) Is Nothing Then
                        Try
                            typeDef.ColumnFieldInfos(i).SetValue(clsRet, oValue)
                        Catch ex As Exception When typeDef.ColumnFieldInfos(i) Is Nothing
                            Throw New ApplicationException(String.Format("Field with index {0} defined as custom, but to custom field handler was defined!", i))
                        End Try
                    Else
                        typeDef.CustomSetHandlers(i).Invoke(clsRet, oValue)
                    End If
                End If
            Next
            If typeDef.RowCompleteHandler IsNot Nothing Then typeDef.RowCompleteHandler.Invoke(clsRet)
            Return clsRet
        End Function
    End Class
End Namespace